package com.fivestar.finance.library.widget.pulltorefresh;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;

import com.fivestar.finance.library.R;
import com.fivestar.finance.library.utils.ResourceUtil;
import com.fivestar.finance.library.utils.logger.Logger;


/**
 * Created by Oleksii Shliama on 22/12/2014.
 * https://dribbble.com/shots/1650317-Pull-to-Refresh-Rentals
 */
public class SunRefreshView extends BaseRefreshView implements Animatable {

  private static final float SCALE_START_PERCENT = 0.5f;
  private static final int ANIMATION_DURATION = 1000;

  private final static float SKY_RATIO = 0.65f;
  private static final float SKY_INITIAL_SCALE = 1.05f;

  private final static float TOWN_RATIO = 0.22f;
  private static final float TOWN_INITIAL_SCALE = 1.20f;
  private static final float TOWN_FINAL_SCALE = 1.30f;

  private static final float SUN_FINAL_SCALE = 0.75f;
  private static final float SUN_INITIAL_ROTATE_GROWTH = 1.2f;
  private static final float SUN_FINAL_ROTATE_GROWTH = 1.5f;

  private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();

  private PullToRefreshView mParent;
  private Matrix mMatrix;
  private Animation mAnimation;

  private int mTop;
  private int mScreenWidth;

  private int mSkyHeight;
  private float mSkyTopOffset;
  private float mSkyMoveOffset;

  private int mTownHeight;
  private float mTownInitialTopOffset;
  private float mTownFinalTopOffset;
  private float mTownMoveOffset;

  private int mSunSize = 100;
  private float mSunLeftOffset;
  private float mSunTopOffset;

  private float mPercent = 0.0f;
  private float mRotate = 0.0f;

  private Bitmap mSky;
  private Bitmap mSun;
  private Bitmap mTown;

  private boolean isRefreshing = false;

  private boolean isCreateBitmap = false;

  public SunRefreshView(Context context, final PullToRefreshView parent) {
    super(context, parent);
    mParent = parent;
    mMatrix = new Matrix();

    setupAnimations();
    parent.post(new Runnable() {
      @Override public void run() {
        initiateDimens(parent.getWidth());
      }
    });
  }

  public void initiateDimens(int viewWidth) {
    Logger.d("viewWidth:" + viewWidth);
    if (viewWidth <= 0 || viewWidth == mScreenWidth) return;

    mScreenWidth = viewWidth;
    mSkyHeight = (int) (SKY_RATIO * mScreenWidth);
    mSkyTopOffset = (mSkyHeight * 0.38f);
    mSkyMoveOffset = ResourceUtil.convertDpToPixel(getContext(), 15);

    mTownHeight = (int) (TOWN_RATIO * mScreenWidth);
    mTownInitialTopOffset = (mParent.getTotalDragDistance() - mTownHeight * TOWN_INITIAL_SCALE);
    mTownFinalTopOffset = (mParent.getTotalDragDistance() - mTownHeight * TOWN_FINAL_SCALE);
    mTownMoveOffset = ResourceUtil.convertDpToPixel(getContext(), 10);

    mSunLeftOffset = 0.3f * (float) mScreenWidth;
    mSunTopOffset = (mParent.getTotalDragDistance() * 0.1f);

    mTop = -mParent.getTotalDragDistance();

    createBitmaps();
  }

  private void createBitmaps() {
    Logger.d("createBitmaps");
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;

    mSky = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sky, options);
    mSky = Bitmap.createScaledBitmap(mSky, mScreenWidth, mSkyHeight, true);
    mTown =
        BitmapFactory.decodeResource(getContext().getResources(), R.drawable.buildings, options);
    mTown = Bitmap.createScaledBitmap(mTown, mScreenWidth, (int) (mScreenWidth * TOWN_RATIO), true);

    mSun = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sun, options);
    mSun = Bitmap.createScaledBitmap(mSun, mSunSize, mSunSize, true);

    Logger.d("mTown:" + mTown.toString());
    Logger.d("mSky:" + mSky.toString());
    Logger.d("mSun:" + mSun.toString());
    isCreateBitmap = true;
  }

  @Override public void setPercent(float percent, boolean invalidate) {
    setPercent(percent);
    if (invalidate) setRotate(percent);
  }

  @Override public void offsetTopAndBottom(int offset) {
    mTop += offset;
    invalidateSelf();
  }

  @Override public void draw(Canvas canvas) {
    if (mScreenWidth <= 0) return;

    final int saveCount = canvas.save();

    canvas.translate(0, mTop);
    canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance());

    drawSky(canvas);
    drawSun(canvas);
    drawTown(canvas);

    canvas.restoreToCount(saveCount);
  }

  private void drawSky(Canvas canvas) {
    Matrix matrix = mMatrix;
    matrix.reset();

    float dragPercent = Math.min(1f, Math.abs(mPercent));

    float skyScale;
    float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
    if (scalePercentDelta > 0) {
      /** Change skyScale between {@link #SKY_INITIAL_SCALE} and 1.0f depending on {@link #mPercent} */
      float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
      skyScale = SKY_INITIAL_SCALE - (SKY_INITIAL_SCALE - 1.0f) * scalePercent;
    } else {
      skyScale = SKY_INITIAL_SCALE;
    }

    float offsetX = -(mScreenWidth * skyScale - mScreenWidth) / 2.0f;
    float offsetY = (1.0f - dragPercent) * mParent.getTotalDragDistance() - mSkyTopOffset
        // Offset canvas moving
        - mSkyHeight * (skyScale - 1.0f) / 2 // Offset sky scaling
        + mSkyMoveOffset * dragPercent; // Give it a little move top -> bottom

    matrix.postScale(skyScale, skyScale);
    matrix.postTranslate(offsetX, offsetY);
    canvas.drawBitmap(mSky, matrix, null);
  }

  private void drawTown(Canvas canvas) {
    Matrix matrix = mMatrix;
    matrix.reset();

    float dragPercent = Math.min(1f, Math.abs(mPercent));

    float townScale;
    float townTopOffset;
    float townMoveOffset;
    float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
    if (scalePercentDelta > 0) {
      /**
       * Change townScale between {@link #TOWN_INITIAL_SCALE} and {@link #TOWN_FINAL_SCALE} depending on {@link #mPercent}
       * Change townTopOffset between {@link #mTownInitialTopOffset} and {@link #mTownFinalTopOffset} depending on {@link #mPercent}
       */
      float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
      townScale = TOWN_INITIAL_SCALE + (TOWN_FINAL_SCALE - TOWN_INITIAL_SCALE) * scalePercent;
      townTopOffset =
          mTownInitialTopOffset - (mTownFinalTopOffset - mTownInitialTopOffset) * scalePercent;
      townMoveOffset = mTownMoveOffset * (1.0f - scalePercent);
    } else {
      float scalePercent = dragPercent / SCALE_START_PERCENT;
      townScale = TOWN_INITIAL_SCALE;
      townTopOffset = mTownInitialTopOffset;
      townMoveOffset = mTownMoveOffset * scalePercent;
    }

    float offsetX = -(mScreenWidth * townScale - mScreenWidth) / 2.0f;
    float offsetY = (1.0f - dragPercent) * mParent.getTotalDragDistance() // Offset canvas moving
        + townTopOffset - mTownHeight * (townScale - 1.0f) / 2 // Offset town scaling
        + townMoveOffset; // Give it a little move

    matrix.postScale(townScale, townScale);
    matrix.postTranslate(offsetX, offsetY);

    canvas.drawBitmap(mTown, matrix, null);
  }

  private void drawSun(Canvas canvas) {
    Matrix matrix = mMatrix;
    matrix.reset();

    float dragPercent = mPercent;
    if (dragPercent > 1.0f) { // Slow down if pulling over set height
      dragPercent = (dragPercent + 9.0f) / 10;
    }

    float sunRadius = (float) mSunSize / 2.0f;
    float sunRotateGrowth = SUN_INITIAL_ROTATE_GROWTH;

    float offsetX = mSunLeftOffset;
    float offsetY = mSunTopOffset + (mParent.getTotalDragDistance() / 2) * (1.0f - dragPercent)
        // Move the sun up
        - mTop; // Depending on Canvas position

    float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
    if (scalePercentDelta > 0) {
      float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
      float sunScale = 1.0f - (1.0f - SUN_FINAL_SCALE) * scalePercent;
      sunRotateGrowth += (SUN_FINAL_ROTATE_GROWTH - SUN_INITIAL_ROTATE_GROWTH) * scalePercent;

      matrix.preTranslate(offsetX + (sunRadius - sunRadius * sunScale),
          offsetY * (2.0f - sunScale));
      matrix.preScale(sunScale, sunScale);

      offsetX += sunRadius;
      offsetY = offsetY * (2.0f - sunScale) + sunRadius * sunScale;
    } else {
      matrix.postTranslate(offsetX, offsetY);

      offsetX += sunRadius;
      offsetY += sunRadius;
    }

    matrix.postRotate((isRefreshing ? -360 : 360) * mRotate * (isRefreshing ? 1 : sunRotateGrowth),
        offsetX, offsetY);

    canvas.drawBitmap(mSun, matrix, null);
  }

  public void setPercent(float percent) {
    mPercent = percent;
  }

  public void setRotate(float rotate) {
    mRotate = rotate;
    invalidateSelf();
  }

  public void resetOriginals() {
    setPercent(0);
    setRotate(0);
  }

  @Override protected void onBoundsChange(Rect bounds) {
    super.onBoundsChange(bounds);
  }

  @Override public void setBounds(int left, int top, int right, int bottom) {
    super.setBounds(left, top, right, mSkyHeight + top);
  }

  @Override public boolean isRunning() {
    return false;
  }

  @Override public void start() {
    mAnimation.reset();
    isRefreshing = true;
    mParent.startAnimation(mAnimation);
  }

  @Override public void stop() {
    mParent.clearAnimation();
    isRefreshing = false;
    resetOriginals();
  }

  private void setupAnimations() {
    mAnimation = new Animation() {
      @Override public void applyTransformation(float interpolatedTime, Transformation t) {
        setRotate(interpolatedTime);
      }
    };
    mAnimation.setRepeatCount(Animation.INFINITE);
    mAnimation.setRepeatMode(Animation.RESTART);
    mAnimation.setInterpolator(LINEAR_INTERPOLATOR);
    mAnimation.setDuration(ANIMATION_DURATION);
  }

  public boolean isCreateBitmap() {
    return isCreateBitmap;
  }
}
